﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Loki.Resources;
using Loki.Utils;

namespace Loki.Commands
{
    /// <summary>
    /// Loki default command service.
    /// </summary>
    public class LokiCommandService : LokiService, ICommandService
    {
        #region Private storage
        private Dictionary<string, List<WeakReference<ICommandHandler>>> _CommandHandlers = null;
        private object _commandHandlerLock = new object();

        private Dictionary<string, List<WeakReference<ICommand>>> _Commands = null;
        private object _commandLock = new object();
        #endregion

        #region Handlers management
        private void CleanReferences()
        {
            foreach (var L_Value in _CommandHandlers.Values)
            {
                L_Value.RemoveAll(s => !s.IsAlive);
            }

            List<string> L_DeadHandlersKeys = _CommandHandlers.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList();
            foreach (var L_Key in L_DeadHandlersKeys)
            {
                _CommandHandlers.Remove(L_Key);
            }

            foreach (var L_Value in _Commands.Values)
            {
                L_Value.RemoveAll(s => !s.IsAlive);
            }

            List<string> L_DeadCommandsKeys = _Commands.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList();
            foreach (var L_Key in L_DeadCommandsKeys)
            {
                _Commands.Remove(L_Key);
            }
        }

        /// <summary>
        /// Gets the handlers.
        /// </summary>
        /// <param name="P_Command">The command.</param>
        public IEnumerable<ICommandHandler> GetHandlers(ICommand P_Command)
        {
            // initialize return
            IEnumerable<ICommandHandler> L_Return = null;

            // lock collections
            Monitor.Enter(_commandHandlerLock);
            Monitor.Enter(_commandLock);

            // remove dead references
            CleanReferences();

            string L_CommandName = P_Command.Tag;

            if (_Commands.ContainsKey(L_CommandName) && _Commands[L_CommandName].FirstOrDefault(x => x.IsAlive && x.Target == P_Command) != null)
            {
                // registered command
                if (_CommandHandlers.ContainsKey(P_Command.Tag))
                {
                    // registerd handler
                    L_Return = _CommandHandlers[P_Command.Tag].Where(x => x.IsAlive).Select(x => x.Target);
                }
            }

            // unlock collections
            Monitor.Exit(_commandLock);
            Monitor.Exit(_commandHandlerLock);

            // return
            return L_Return;
        }
        #endregion

        #region Handler registering
        /// <summary>
        /// Creates and register a command handler fo the specified command.
        /// </summary>
        /// <param name="P_Command">The command.</param>
        /// <param name="P_CanExecute">The CanExecute function.</param>
        /// <param name="P_Execute">The Execute function.</param>
        /// <param name="P_State">The command state handler.</param>
        /// <param name="P_ConfirmDelegate">The confirm delegate.</param>
        /// <returns>
        /// You must hold a reference on the returned command handler as long as the listener observes the command.
        /// </returns>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "L_TempHandler is disposed on error, not in normal work")]
        public ICommandHandler CreateHandler(ICommand P_Command, Action<object, CanExecuteCommandEventArgs> P_CanExecute, Action<object, CommandEventArgs> P_Execute, ICommandAware P_State, Func<CommandEventArgs, bool> P_ConfirmDelegate)
        {
            LokiCommandHandler L_ReturnHandler = null;
            LokiCommandHandler L_TempHandler = null;
            try
            {
                L_TempHandler = LokiCommandHandler.Create(P_CanExecute, P_Execute, P_State, P_ConfirmDelegate);
                AddHandler(P_Command, L_TempHandler);
                L_ReturnHandler = L_TempHandler;
                L_TempHandler = null;
            }
            finally
            {
                if (L_TempHandler != null)
                {
                    L_TempHandler.Dispose();
                }
            }

            return L_ReturnHandler;
        }

        public ICommandHandler CreateHandler(ICommand P_Command, Action<object, CanExecuteCommandEventArgs> P_CanExecute, Action<object, CommandEventArgs> P_Execute, ICommandAware P_State)
        {
            return CreateHandler(P_Command, P_CanExecute, P_Execute, P_State, null);
        }

        ICommandHandler ICommandService.CreateHandler(ICommand P_Command, Action<object, CanExecuteCommandEventArgs> P_CanExecute, Action<object, CommandEventArgs> P_Execute)
        {
            return CreateHandler(P_Command, P_CanExecute, P_Execute, null);
        }

        /// <summary>
        /// Adds the specified <see cref="ICommandHandler"/> for the specified command.
        /// </summary>
        /// <param name="P_Command">The command.</param>
        /// <param name="P_Handler">The command handler.</param>
        public void AddHandler(ICommand P_Command, ICommandHandler P_Handler)
        {
            lock (_commandHandlerLock)
            {
                if (!_CommandHandlers.ContainsKey(P_Command.Tag))
                {
                    _CommandHandlers.Add(P_Command.Tag, new List<WeakReference<ICommandHandler>>());
                }

                _CommandHandlers[P_Command.Tag].Add(WeakReference<ICommandHandler>.Create(P_Handler));

                if (P_Handler.State != null)
                {
                    LokiServices.Event.RegisterChanged(P_Handler.State, this, (c, o, e) => c.CanExecuteChangedCallback(P_Command.Tag));
                }
            }
        }

        private void CanExecuteChangedCallback(string P_CommandName)
        {
            lock (_commandLock)
            {
                if (_Commands.ContainsKey(P_CommandName))
                {
                    foreach (var L_Item in _Commands[P_CommandName])
                    {
                        if (L_Item.IsAlive)
                        {
                            L_Item.Target.RefreshState();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Removes the specified <see cref="ICommandHandler"/> for the specified command.
        /// </summary>
        /// <param name="P_Command">The command name.</param>
        /// <param name="P_Handler">The command handler.</param>
        public void RemoveHandler(ICommand P_Command, ICommandHandler P_Handler)
        {
            lock (_commandHandlerLock)
            {
                if (_CommandHandlers.ContainsKey(P_Command.Tag))
                {
                    WeakReference<ICommandHandler> L_Reference = _CommandHandlers[P_Command.Tag].FirstOrDefault(x => x.Target == P_Handler);
                    if (L_Reference != null)
                    {
                        _CommandHandlers[P_Command.Tag].Remove(L_Reference);
                    }
                    else
                    {
                        Log.DebugFormat(LogStrings.CommandService_AlreadyUnregisteredHandler, P_Command);
                    }

                    CanExecuteChangedCallback(P_Command.Tag);
                }
            }
        }
        #endregion

        #region Command registering
        ICommand ICommandService.CreateCommand(string P_CommandName)
        {
            return CreateCommand(false, P_CommandName);
        }

        /// <summary>
        /// Creates and registers a new command for the specified command name.
        /// </summary>
        /// <param name="P_DeepCommand">Set to true if the command is a deep command.</param>
        /// <param name="P_CommandTag">Name of the command.</param>
        /// <returns>
        /// The registered command.
        /// </returns>
        public ICommand CreateCommand(bool P_DeepCommand, string P_CommandTag)
        {
            LokiRoutedCommand L_Command = new LokiRoutedCommand(P_DeepCommand, P_CommandTag);
            RegisterCommand(L_Command);
            return L_Command;
        }

        ICommand ICommandService.CreateCommand()
        {
            return CreateCommand(false);
        }

        /// <summary>
        /// Creates and registers a new command.
        /// </summary>
        /// <param name="P_DeepCommand">Set to true if the command is a deep command.</param>
        /// <returns>The registerd command.</returns>
        public ICommand CreateCommand(bool P_DeepCommand)
        {
            return CreateCommand(P_DeepCommand, Guid.NewGuid().ToString());
        }

        /// <summary>
        /// Registers a command.
        /// </summary>
        /// <param name="P_Command">The command.</param>
        public void RegisterCommand(ICommand P_Command)
        {
            lock (_commandLock)
            {
                // check command grouping
                if (!_Commands.ContainsKey(P_Command.Tag))
                {
                    _Commands.Add(P_Command.Tag, new List<WeakReference<ICommand>>());
                }

                _Commands[P_Command.Tag].Add(WeakReference<ICommand>.Create(P_Command));
            }
        }

        /// <summary>
        /// Unregister a command.
        /// </summary>
        /// <param name="P_Command">The command.</param>
        public void UnregisterCommand(ICommand P_Command)
        {
            lock (_commandLock)
            {
                if (_Commands.ContainsKey(P_Command.Tag))
                {
                    WeakReference<ICommand> L_Reference = _Commands[P_Command.Tag].FirstOrDefault(x => x.Target == P_Command);
                    if (L_Reference != null)
                    {
                        // Remove reference
                        _Commands[P_Command.Tag].Remove(L_Reference);
                    }
                    else
                    {
                        Log.DebugFormat(LogStrings.CommandService_AlreadyUnregisteredCommand, P_Command.Tag);
                    }
                }
            }
        }
        #endregion

        #region Identifiers
        /// <summary>
        /// Gets the service name.
        /// </summary>
        /// <value>
        /// The service name.
        /// </value>
        public override string Name
        {
            get { return NAME; }
        }

        private const string NAME = "Loki default command service";

        private const string _ID = "{2968254a-57dc-4f85-bf9d-5e567efe5850}";

        /// <summary>
        /// Gets the service identifier.
        /// </summary>
        /// <value>
        /// The service identifier.
        /// </value>
        public override Guid ID
        {
            get { return new Guid(_ID); }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LokiCommandService"/> class.
        /// </summary>
        public LokiCommandService()
        {
            _Commands = new Dictionary<string, List<WeakReference<ICommand>>>();
            _CommandHandlers = new Dictionary<string, List<WeakReference<ICommandHandler>>>();
        }
        #endregion

        #region IDisposable
        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="LokiService"/> class and optionally releases the managed resources.
        /// </summary>
        /// <param name="P_Disposing">Set <strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected override void Dispose(bool P_Disposing)
        {
            if (P_Disposing)
            {
                _CommandHandlers.Clear();
                _Commands.Clear();
            }

            base.Dispose(P_Disposing);
        }
        #endregion
    }
}